package liquidfs;

import java.io.*;
import java.text.*;
import java.util.*;
import java.util.logging.*;

import liquidfs.core.*;
import liquidfs.data.*;
import liquidfs.ui.cmd.*;
import liquidfs.ui.ftp.*;
import liquidfs.ui.net.*;
import liquidfs.ui.vfs.*;
import liquidfs.ui.web.*;
import utility.*;

public class Application implements Runnable {

   // Configuration
   private Configuration configuration;
   
   // Core components
   private OperationProcessor processor;
	private LiquidFileSystem fs;
	private HandleManager hm;
	private MetadatabaseInterface mdb;
	private Context context;
	private Journal journal;
	private ProblemBox problemBox;
	
	// User interface components
	private VirtualFileSystem vfs;
	private MessageServer server;
	private WebInterface web;
	private FtpServerInterface ftp;
	
	// Log
	private static Logger log = Logger.getLogger("liquidfs");
	
	/**
    * Create a new LiquidFS instance with the given configuration.
    * @param config The configuration parameters for this instance of LiquidFS
    * @throws FileNotFoundException 
    */
   public Application (Configuration configuration) {
      
      // Set the configuration
      this.configuration = configuration;
   }
	
	/**
	 * Create a new LiquidFS instance with the given configuration.
	 * @param config The configuration parameters for this instance of LiquidFS
	 * @throws FileNotFoundException 
	 */
	public Application (String configurationFilename) {
	   
	   // Set the configuration
	   try {
	      this.configuration = Configuration.loadConfiguration(this, configurationFilename);
	   }
	   catch (IOException e) {
	      throw new UserUncheckedException("Unable to load configuration file " + configurationFilename + ": " + e.getLocalizedMessage());
	   }
	}
	
	public Application () {
	   this.configuration = Configuration.createDefaultConfiguration(this);
	}
	
	private void initialize () {
      
	   // Create core components
	   this.processor = new OperationProcessor(this);
	   this.fs = new LiquidFileSystem(this);
	   this.hm = new HandleManager(this);
      this.mdb = new MetadatabaseInterface(this);
      this.context = new Context(this.mdb);
      this.journal = new Journal(this);
      this.problemBox = new ProblemBox(this);
      
      // Create the user interface components
      this.server = new MessageServer (this);
      this.vfs = new WindowsVirtualFileSystem(this);
      this.web = new WebInterface(this);
      this.ftp = new FtpServerInterface(this);
      
      // Check the journal
      try {
         JournalEntry entry = this.journal.read();
         
         if (entry != null) {
            log.warning("LiquidFS was terminated while performing a " + entry.getCurrentOperation().toString() + "; correcting...");
            entry.getCurrentOperation().fix(this, entry.getCurrentOperationState(), entry.getData());
            this.journal.endEntry();
         }
      }
      catch (IOException e) {
         throw new UserUncheckedException("Unable to read journal entry: " + e.getMessage(), e);
      }
      
	}
	
	public OperationProcessor getOperationProcessor () {
      return this.processor;
   }
	
	public LiquidFileSystem getFileSystem () {
	   return this.fs;
	}
	
	public HandleManager getHandleManager () {
	   return this.hm;
	}
	
	public MetadatabaseInterface getMetadatabase () {
	   return this.mdb;
	}
	
	public Journal getJournal () {
	   return this.journal;
	}
	
	public ProblemBox getProblemBox () {
	   return this.problemBox;
	}
	
	public Configuration getConfiguration () {
		return this.configuration;
	}
	
	public Context getContext () {
	   return this.context;
	}
	
	public void run () {
		
	   initialize();
	   
		// Create a thread for the virtual file system
	   if (this.getConfiguration().isVirtualFileSystemEnabled()) {
	      Thread virtualFileSystemThread = new Thread(this.vfs, "VFS (" + this.getConfiguration().getLocalDevice().getName() + ")");
		   virtualFileSystemThread.start();
	   }
	   
	   if (this.getConfiguration().isFtpServerEnabled()) {
	      Thread ftpServerThread = new Thread(this.ftp, "FTP (" + this.getConfiguration().getLocalDevice().getName() + ")");
         ftpServerThread.start();
	   }
		
	   Thread processorThread = new Thread(this.processor, "Operation Processor (" + this.getConfiguration().getLocalDevice().getName() + ")");
	   processorThread.start();
		
		// Create a thread for the server and run it
		Thread serverThread = new Thread(this.server, "Message Server (" + this.getConfiguration().getLocalDevice().getName() + ")");
		serverThread.start();
		
		// Create a thread for the embedded web server and run it (Note: It creates the thread itself)
		if (configuration.isWebInterfaceEnabled()) {
		   this.web.start();
	   }
	}
	
	public static void main (String[] args) throws Exception {
	   
	   // Turn off default logging
	   log.setUseParentHandlers(false);
	   log.addHandler(new LogHandler());
	   log.setLevel(Level.FINE);
	   
	   try {
//	      log.info("Running remote application...");
//	      Application remoteApp = new Application("filesystems/remote.xml");
//	      remoteApp.getConfiguration().setVirtualFileSystemEnabled(false);
//	   
//	      Thread remoteThread = new Thread(remoteApp);
//	      remoteThread.start();

	      // Create an instance of the application
	      log.info("Running application...");	      
         Application app = new Application ("filesystems/local.xml");
   	   
   	   // Run the app and all it's threads
         //app.getConfiguration().setVirtualFileSystemEnabled(false);
	      app.run ();
	      
	      log.info("Starting console...");
	      CommandLine console = new CommandLine(app);
	      console.run();
	   }
	   catch (UserUncheckedException e) {
	      log.severe(e.getLocalizedMessage());
	   }
	}

	public static boolean isJar() {
	   String className = Application.class.getName().replace('.', '/');
	   String classJar =  Application.class.getResource("/" + className + ".class").toString();
	   return classJar.startsWith("jar:");
	}
	
	public static class LogHandler extends Handler {

	   private DateFormat format;
	   private Map<Level, String> levelColor = new HashMap<Level, String>();
	   private static final String ESCAPE = (char) 27 + "[";
	   private static final String BOLD_BLACK   = ESCAPE + "1;30m";
	   private static final String BOLD_RED     = ESCAPE + "1;31m";
	   private static final String BOLD_GREEN   = ESCAPE + "1;32m";
	   private static final String BOLD_YELLOW  = ESCAPE + "1;33m";
	   private static final String BOLD_BLUE    = ESCAPE + "1;34m";
	   private static final String BOLD_MAGENTA = ESCAPE + "1;35m";
	   private static final String BOLD_CYAN    = ESCAPE + "1;36m";
	   private static final String BOLD_WHITE   = ESCAPE + "1;37m";
	   private static final String NORMAL       = ESCAPE + "0m";
	   
	   public LogHandler () {
	      format = DateFormat.getDateTimeInstance();
	      levelColor.put(Level.FINEST, BOLD_BLUE);
	      levelColor.put(Level.FINER, BOLD_BLUE);
	      levelColor.put(Level.FINE, BOLD_BLUE);
	      levelColor.put(Level.INFO, BOLD_BLUE);
	      levelColor.put(Level.SEVERE, BOLD_RED);
	      levelColor.put(Level.WARNING, BOLD_YELLOW);
	   }
	   
      @Override
      public void close() throws SecurityException {
         // TODO Auto-generated method stub
         
      }

      @Override
      public void flush() {
         // TODO Auto-generated method stub
         
      }

      @Override
      public void publish(LogRecord record) {
         Date date = new Date(record.getMillis());
         String dateString = format.format(date);
         
         // Add the date and a color according to the severity of the message
         System.out.print(dateString + ": ");
         System.out.print(levelColor.get(record.getLevel()));
         
         // Break the messaage into lines
         String[] lines = record.getMessage().split("\n");
         
         // Print the first line as is
         System.out.print(lines[0]);
         
         // Indent subsequent lines
         for (int i = 1; i < lines.length; ++i) {
            System.out.println();
            for (int j = 0; j < dateString.length() + 2; ++j) 
               System.out.print(" ");
            System.out.print(lines[i]);
         }
         
         // Reset the formatting
         System.out.print(NORMAL);
         System.out.println();
      }
	   
	}
}
